<script setup lang="ts">
// Spec: 在页面中展示的规格状态的类型
import type { Sku, Spec } from "@/types/Goods";

// 导入 募级算法
import bwPowerSet from "@/vendors/power-set";

import type { Data } from "@/stores/goodsStore";

// specs: 在页面中展示的规格状态
const props = defineProps<{ specs: Spec[]; skus: Sku[]; skuId?: string }>();

// 获取规格查询对象
const pathMap = createPathMap();

// 声明当前组件触发的自定义事件
const emit = defineEmits<{
  (e: "complete", data: Data): void;
  (e: "unComplete"): void;
}>();

// 规格查询对象类型
interface PathMap {
  [key: string]: string | null;
}

// 用户选择规格时的  UI 状态类型
interface UIStatus {
  // 选中
  selected: boolean;
  // 禁用
  disabled: boolean;
}

// 根据规格状态创建其对应的 UI 状态
function createUIStatus(specs: Spec[]) {
  // UI 状态数组
  const UIStatus: UIStatus[][] = [];
  // 遍历真实规格数据
  specs.forEach((spec) => {
    // 创建每一个规格定义的数组
    const group: UIStatus[] = [];
    // 把规格组对象添加到拷贝结果数组中
    UIStatus.push(group);
    // 遍历具体的规格选项
    spec.values.forEach(() => {
      // 设置每一个规格选择的选中状态和禁用状态
      group.push({ selected: false, disabled: false });
    });
  });
  // 返回 UI 状态数组
  return UIStatus;
}

// 创建 UI 状态
const UIState = reactive(createUIStatus(props.specs));

// 设置规格的选中状态
function updateSelected(index: number, i: number) {
  // 获取当前用户点击的规格
  const current = UIState[index][i];
  // 如果当前规格已经是禁用状态  不能被选择  所以阻止代码继续执行
  if (current.disabled) return;
  // 判断如果用户点击的规格是已选中的
  if (current.selected) {
    // 取消
    current.selected = false;
  } else {
    // 先把该规格中的所有规格取消选中
    UIState[index].forEach((item) => (item.selected = false));
    // 把当前用户点击的规格设置为选中
    current.selected = true;
  }
  // 实现用户选择时的规格
  updateDisabled();
  // 把用户选择的规格对应到商品信息传递到父组件
  sendGoodsInfoToParent();
}

// 募级算法
// 创建规格查询字典
function createPathMap(): PathMap {
  // 用于存储最终的规格查询对象
  const pathMap: PathMap = {};
  // 过滤出有存库的商品规格组合
  props.skus
    .filter((sku) => sku.inventory > 0)
    // 遍历有库存的商品规格组合
    .forEach((sku) => {
      // 把当前的规格组合中的规格名称临时存到一个数组中
      const valueName = sku.specs.map((spec) => spec.valueName);
      // 获取用户可以选择的所有可能的规格及规格组合
      const sets = bwPowerSet(valueName).filter((set) => set.length > 0);
      // 获取当前商品的规格数量 把用于判断某个规格是否完整的
      const max = valueName.length;
      // 遍历用户可以选择的所有可能的规格及规格组合
      sets.forEach((set) => {
        // 把规格名称 以 _ 拼接
        const key = set.join("_");
        // 判断规格查询对象中是否已经存储了当前规格
        if (!(key in pathMap)) {
          // 如果当前规格是完整的
          if (set.length === max) {
            // 把当前规格或组合添加到规格查询对象中并赋值 sku id
            pathMap[key] = sku.id;
          } else {
            // 把当前规格或规格组合添加到规格查询对象中
            pathMap[key] = null;
          }
        }
      });
    });
  // 返回查询对象
  return pathMap;
}

// 设置规格的禁用状态
function updateDisabled() {
  // 遍历规格组
  props.specs.forEach((spec, index) => {
    // 获取用户选择的规格数据中的具体规格
    const selected = getSelected();
    // 遍历每一个具体的规格
    spec.values.forEach((value, i) => {
      // 如果当前规格已经被选中 声明它可以选 不需要禁用
      if (UIState[index][i].selected) return;
      // 把当前规格名称放入用户选择的规格数组名称中
      selected[index] = value.name;
      // 检测当前规格是否可以选择
      const key = selected.filter((name) => name).join("_");
      // 检测当前规格是否可以选择  如果不能选择  设置当前规格的 disabled 为 true
      UIState[index][i].disabled = !(key in pathMap);
    });
  });
}

// 获取用户选择的规格名称数组
function getSelected() {
  // 声明用于存储用户选择的规格名称数组
  const names: (string | undefined)[] = [];
  // 遍历规格组
  props.specs.forEach((spec, index) => {
    // 查找当前规格组中被选中的规格的索引
    const selectedIndex = UIState[index].findIndex((item) => item.selected);
    // 如果找到了
    if (selectedIndex !== -1) {
      // 把该规格放在他自己的位置上
      names[index] = spec.values[selectedIndex].name;
    } else {
      // 如果没找到 当前规格使用 undefined 进行占位
      names[index] = undefined;
    }
  });
  // console.log(names);
  // 返回用户选择的规格名称数组
  return names;
}

// 设置规格默认选中
function setDefaultSelected() {
  // 判断
  if (typeof props.skuId !== "undefined") {
    // 查找默认选中的规格对象 从中获取规格名称
    const sku = props.skus.find((sku) => sku.id === props.skuId);
    // 如果没有找到默认选中的规格对象  阻止程序继续运行
    if (typeof sku === "undefined") return;
    // 获取默认选中的规格名称 数组
    const valueNames = sku.specs.map((spec) => spec.valueName);
    // 遍历页面中展示的每一个具体的规格
    props.specs.forEach((spec, index) =>
      // 遍历页面中展示的每一个具体的规格
      spec.values.forEach((value, i) => {
        // 查看当前遍历的规格的名称是否被包含在默认选中的规格名称数组中
        if (valueNames.includes(value.name)) {
          // 设置当前规格的选中状态
          UIState[index][i].selected = true;
        }
      })
    );
  }
}

// 向父组件传递商品信息
function sendGoodsInfoToParent() {
  // 获取用户选择的规格名称数组
  const names = getSelected().filter((name) => name);
  // 判断用户是否选择了完整的规格信息
  if (names.length === props.specs.length) {
    // 获取规格  skuId
    const skuId = pathMap[names.join("_")]!;
    // 根据 skuId 查找规格对象
    const sku = props.skus.find((sku) => sku.id === skuId)!;
    // 如果找到了规格对象
    // 触发自定义事件  向外部传递最新商品信息
    emit("complete", {
      price: sku.price,
      oldPrice: sku.oldPrice,
      inventory: sku.inventory,
      skuId,
    });
  } else {
    // 用户没有选择完整的规则
    emit("unComplete");
  }
}

// 更新规格的禁用状态
updateDisabled();

// 设置规格的默认选中状态
setDefaultSelected();
</script>

<template>
  <div class="goods-sku">
    <!-- 循环 -->
    <dl v-for="(spec, index) in specs" :key="spec.id">
      <dt>{{ spec.name }}</dt>
      <dd>
        <template v-for="(values, i) in spec.values">
          <img
            :class="{
              selected: UIState[index][i].selected,
              disabled: UIState[index][i].disabled,
            }"
            v-if="values.picture"
            @click="updateSelected(index, i)"
            :src="values.picture"
            :alt="values.name"
          />
          <span
            @click="updateSelected(index, i)"
            :class="{
              selected: UIState[index][i].selected,
              disabled: UIState[index][i].disabled,
            }"
            v-else
          >
            {{ values.name }}</span
          >
        </template>
      </dd>
    </dl>
  </div>
</template>

<style scoped lang="less">
.sku-state-mixin () {
  border: 1px solid #e4e4e4;
  margin-right: 10px;
  cursor: pointer;
  margin-bottom: 10px;
  &.selected {
    border-color: @xtxColor;
  }
  &.disabled {
    opacity: 0.6;
    border-style: dashed;
    cursor: not-allowed;
  }
}
.goods-sku {
  padding-left: 10px;
  padding-top: 20px;
  dl {
    display: flex;
    padding-bottom: 5px;
    align-items: center;
    dt {
      width: 50px;
      color: #999;
    }
    dd {
      flex: 1;
      color: #666;
      > img {
        width: 50px;
        height: 50px;
        .sku-state-mixin ();
      }
      > span {
        display: inline-block;
        height: 30px;
        line-height: 28px;
        padding: 0 20px;
        .sku-state-mixin ();
      }
    }
  }
}
</style>
